// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 该文件实现了source，一个缓冲符文读取器
// 专门用于扫描Go代码：读取
// ASCII字符，保持当前（行、列）
// 位置信息，以及记录最新读取的
// 源代码段都得到了高度优化。
// 这个文件是自包含的（go tool compile source.go 
// compiles），因此可以制作成自己的包。

package syntax

import (
	"io"
	"unicode/utf8"
)

// 使用三个索引b（begin）、
// r（read）和e（end）访问源缓冲区：
// 
// -如果b>=0，则它指向最近读取的大多数
// 字符段的开头（通常为Go文字）。
// 
// -r指向紧跟在最近的
// read character ch之后的字节，该字符从r-chw开始。
// 
// /-e指向读取到缓冲区的最后一个字节后面的字节。
// 
// 缓冲区内容在buf[e]处终止，带有哨兵
// 字符utf8。RuneSelf。这样就可以用一个“if”来测试
// ASCII字符的常见大小写（参见
// nextch方法）。
// 
// /+----正在使用的内容----+
// /v 
// buf[…已读…|…段…| ch |…未读…| s |…免费…]
// /^^^^ 
// /| | | | 
// b r-chw r e 
// 
// 不变量：-1<=b<r<=e<len（buf）和&buf[e]==sentinel 

type source struct {
	in   io.Reader
	errh func(line, col uint, msg string)

	buf       []byte // 源缓冲区
	ioerr     error  // 待处理的I/O错误，或nil 
	b, r, e   int    // 缓冲区索引（见上文注释）
	line, col uint   // 基于0的ch的源位置
	ch        rune   // 最近读取的字符
	chw       int    // ch的宽度
}

const sentinel = utf8.RuneSelf

func (s *source) init(in io.Reader, errh func(line, col uint, msg string)) {
	s.in = in
	s.errh = errh

	if s.buf == nil {
		s.buf = make([]byte, nextSize(0))
	}
	s.buf[0] = sentinel
	s.ioerr = nil
	s.b, s.r, s.e = -1, 0, 0
	s.line, s.col = 0, 0
	s.ch = ' '
	s.chw = 0
}

// 行号和列号的起始点
const linebase = 1
const colbase = 1

// pos返回s.ch的（行、列）源位置。
func (s *source) pos() (line, col uint) {
	return linebase + s.line, colbase + s.col
}

// 错误报告源位置的错误消息位置s.位置（）。
func (s *source) error(msg string) {
	line, col := s.pos()
	s.errh(line, col, msg)
}

// start启动一个新的活动源段（包括s.ch）。
// 只要没有调用stop，活动段的
// 字节（不包括s.ch）就可以通过调用段进行检索。
func (s *source) start()          { s.b = s.r - s.chw }
func (s *source) stop()           { s.b = -1 }
func (s *source) segment() []byte { return s.buf[s.b : s.r-s.chw] }

// 倒带将扫描仪的读取位置和字符s.ch 
// 倒带到当前活动段的开头，该段不能包含任何换行符（否则位置信息将为
// 不正确）。目前，倒带仅用于处理
// 源序列“.”；不能在活动
// 段之外调用。
func (s *source) rewind() {
	// 确定验证前提条件-倒带很少被称为
	if s.b < 0 {
		panic("no active segment")
	}
	s.col -= uint(s.r - s.b)
	s.r = s.b
	s.nextch()
}

func (s *source) nextch() {
redo:
	s.col += uint(s.chw)
	if s.ch == '\n' {
		s.line++
		s.col = 0
	}

	// 快速常见情况：至少有一个ASCII字符
	if s.ch = rune(s.buf[s.r]); s.ch < sentinel {
		s.r++
		s.chw = 1
		if s.ch == 0 {
			s.error("invalid NUL character")
			goto redo
		}
		return
	}

	// 较慢的一般情况：如果没有完整的符文，则向缓冲区添加更多字节
	for s.e-s.r < utf8.UTFMax && !utf8.FullRune(s.buf[s.r:s.e]) && s.ioerr == nil {
		s.fill()
	}

	// EOF 
	if s.r == s.e {
		if s.ioerr != io.EOF {
			// 确保错误消息
			s.error("I/O error: " + s.ioerr.Error())
			s.ioerr = nil
		}
		s.ch = -1
		s.chw = 0
		return
	}

	s.ch, s.chw = utf8.DecodeRune(s.buf[s.r:s.e])
	s.r += s.chw

	if s.ch == utf8.RuneError && s.chw == 1 {
		s.error("invalid UTF-8 encoding")
		goto redo
	}

	// BOM仅为允许作为文件中的第一个字符
	const BOM = 0xfeff
	if s.ch == BOM {
		if s.line > 0 || s.col > 0 {
			s.error("invalid BOM in the middle of the file")
		}
		goto redo
	}
}

// fill将更多源字节读入s.buf。
// 它返回时缓冲区中至少还有一个字节，或者返回s.ioerr！=无
func (s *source) fill() {
	// 确定要保存的内容
	b := s.r
	if s.b >= 0 {
		b = s.b
		s.b = 0 // 在缓冲区增长或内容向下移动后
	}
	content := s.buf[b:s.e]

	// 增长缓冲区或向下移动内容
	if len(content)*2 > len(s.buf) {
		s.buf = make([]byte, nextSize(len(s.buf)))
		copy(s.buf, content)
	} else if b > 0 {
		copy(s.buf, content)
	}
	s.r -= b
	s.e -= b

	// 读取更多数据：尝试有限次数
	for i := 0; i < 10; i++ {
		var n int
		n, s.ioerr = s.in.Read(s.buf[s.e : len(s.buf)-1]) // /-1为哨兵
		if n < 0 {
			panic("negative read") // 错误的底层io留出空间。读卡器实现
		}
		if n > 0 || s.ioerr != nil {
			s.e += n
			s.buf[s.e] = sentinel
			return
		}
		// n==0 
	}

	s.buf[s.e] = sentinel
	s.ioerr = io.ErrNoProgress
}

// nextSize为给定大小的缓冲区返回下一个更大的大小。
func nextSize(size int) int {
	const min = 4 << 10 // 4K:最小缓冲区大小
	const max = 1 << 20 // 1M:最大缓冲区大小仍然是
	if size < min {
		return min
	}
	if size <= max {
		return size << 1
	}
	return size + max
}
